﻿using System.Text.RegularExpressions;

using Storm.TextEditor.Parser.Objects.Collections;

namespace Storm.TextEditor.Parser.Objects
{
	/// <summary>
	/// A Pattern is a specific string or a RegEx pattern that is used by the parser.
	/// There are two types of patterns, Simple and Complex.
	/// 
	/// Simple Patterns are patterns that consists of a simple fixed string eg. "void" or "for".
	/// Complex Patterns are patterns that consists of RegEx patterns , eg. hex numbers or URLs can be described as RegEx patterns.
	/// </summary>
	public sealed class Pattern
	{
		#region Fields

		private string separators = " .,+-*^\\/()[]{}@:;'?£$#%& \t=<>";

		private string category           = "";
		private string stringPattern      = "";
		private string lowerStringPattern = "";

		private Regex regex     = null;
		private Regex caseRegex = null;

		private PatternList parent        = null;
		private char[]      patternBuffer = null;

		private bool complex   = false;
		private bool separator = false;
		private bool keyword   = false;

		private BracketType bracketType      = BracketType.None;
		private Pattern     matchingBracket  = null;
		private bool        multilineBracket = true;

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the string containing the separators of the Pattern.
		/// </summary>
		public string Separators
		{
			get { return separators; }
			set { separators = value; }
		}

		/// <summary>
		/// Gets or sets the category of the Pattern.
		/// </summary>
		public string Category
		{
			get { return category; }
			set { category = value; }
		}

		/// <summary>
		/// Gets or sets the Pattern as a string.
		/// </summary>
		public string StringPattern
		{
			get { return stringPattern; }
			set
			{
				stringPattern = value;
				lowerStringPattern = stringPattern.ToLower();
			}
		}

		/// <summary>
		/// Gets or sets the Pattern as a lower case string.
		/// </summary>
		public string LowerStringPattern
		{
			get { return lowerStringPattern; }
			set { lowerStringPattern = value; }
		}

		/// <summary>
		/// Gets or sets the parent PatternList of the Pattern.
		/// </summary>
		public PatternList Parent
		{
			get { return parent; }
			set { parent = value; }
		}

		/// <summary>
		/// Gets or sets the Pattern's buffer.
		/// </summary>
		public char[] PatternBuffer
		{
			get { return patternBuffer; }
			set { patternBuffer = value; }
		}

		/// <summary>
		/// Gets or sets whether the Pattern is complex.
		/// </summary>
		public bool IsComplex
		{
			get { return complex; }
			set { complex = value; }
		}

		/// <summary>
		/// Gets or sets whether the Pattern is a separator.
		/// </summary>
		public bool IsSeparator
		{
			get { return separator; }
			set { separator = value; }
		}

		/// <summary>
		/// Gets or sets whether the Pattern is a keyword.
		/// </summary>
		public bool IsKeyword
		{
			get { return keyword; }
			set { keyword = value; }
		}

		/// <summary>
		/// Gets or sets the BracketType of the Pattern.
		/// </summary>
		public BracketType BracketType
		{
			get { return bracketType; }
			set { bracketType = value; }
		}

		/// <summary>
		/// Gets or sets the matching bracket of the Pattern.
		/// </summary>
		public Pattern MatchingBracket
		{
			get { return matchingBracket; }
			set { matchingBracket = value; }
		}

		/// <summary>
		/// Gets or sets whether the Pattern is a multiline bracket.
		/// </summary>
		public bool IsMultilineBracket
		{
			get { return multilineBracket; }
			set { multilineBracket = value; }
		}

		/// <summary>
		/// Gets whether the Pattern contains a separator character.
		/// </summary>
		public bool ContainsSeparator
		{
			get
			{
				for (int i = 0; i < this.StringPattern.Length; i++)
				{
					char c = this.StringPattern[i];
					if (this.Separators.IndexOf(c) >= 0)
						return true;
				}

				return false;
			}
		}

		#endregion

		#region Methods

		#region Public

		/// <summary>
		/// Returns whether the given text has any separators from the given position.
		/// </summary>
		/// <param name="text">Text to search in.</param>
		/// <param name="position">Position to start at.</param>
		/// <returns>Whether the given text has any separators from the given position.</returns>
		public bool HasSeparators(string text, int position)
		{
			return (this.CharIsSeparator(text, position - 1) && this.CharIsSeparator(text, position + stringPattern.Length));
		}

        /// <summary>
        /// Returns the PatternScanResult containing the index of the given text from the given start position.
        /// </summary>
        /// <param name="text">Text to find.</param>
        /// <param name="startPosition">Starting position.</param>
        /// <param name="matchCase">Whether the match should match case.</param>
        /// <param name="separators">Separators to use.</param>
        /// <returns>Found PatternScanResult.</returns>
        public PatternScanResult IndexIn(string text, int startPosition, bool matchCase, string separators)
        {
            return this.IndexIn(text, startPosition, -1, matchCase, separators);
        }

		/// <summary>
		/// Returns the PatternScanResult containing the index of the given text from the given start position.
		/// </summary>
		/// <param name="text">Text to find.</param>
		/// <param name="startPosition">Starting position.</param>
        /// <param name="maxIndex">Maximum index.</param>
		/// <param name="matchCase">Whether the match should match case.</param>
		/// <param name="separators">Separators to use.</param>
		/// <returns>Found PatternScanResult.</returns>
		public PatternScanResult IndexIn(string text, int startPosition, int maxIndex, bool matchCase, string separators)
		{
			if (separators != null)
				this.Separators = separators;

			if (complex == false)
			{
				if (keyword == false)
					return this.SimpleFind(text, startPosition, maxIndex, matchCase);
				else
					return this.SimpleFindKeyword(text, startPosition, maxIndex, matchCase);
			}
			else
			{
				if (keyword == false)
					return this.ComplexFind(text, startPosition, maxIndex);
				else
					return this.ComplexFindKeyword(text, startPosition, maxIndex);
			}
		}

		#endregion

		#region Private

		/// <summary>
		/// Returns whether the character at the given position in the given text is a separator.
		/// </summary>
		/// <param name="text">Text to look in.</param>
		/// <param name="position">Position of the character.</param>
		/// <returns>Whether the character at the given position in the given text is a separator.</returns>
		private bool CharIsSeparator(string text, int position)
		{
			if (position < 0 || position >= text.Length)
				return true;

			char c = text[position];
			if (this.Separators.IndexOf(c) >= 0)
				return true;

			return false;
		}

		/// <summary>
		/// Finds the given text from the given starting position as a simple find.
		/// </summary>
		/// <param name="text">Text to find.</param>
		/// <param name="startPosition">Starting position.</param>
        /// <param name="maxIndex">Maximum index.</param>
		/// <param name="matchCase">Whether the match case.</param>
		/// <returns>Found PatternScanResult.</returns>
		private PatternScanResult SimpleFind(string text, int startPosition, int maxIndex, bool matchCase)
		{
			int position = 0;
			if (matchCase == true)
				position = text.IndexOf(stringPattern, startPosition);
			else
				position = text.ToLower().IndexOf(lowerStringPattern, startPosition);

			PatternScanResult scanResult;
			if (position >= 0)
			{
                if (maxIndex > -1)
                {
                    if (position + this.StringPattern.Length <= maxIndex)
                    {
                        scanResult.Index = position;
                        scanResult.Token = text.Substring(position, this.StringPattern.Length);
                        return scanResult;
                    }
                }
                else
                {
                    scanResult.Index = position;
                    scanResult.Token = text.Substring(position, this.StringPattern.Length);
                    return scanResult;
                }
			}
			else
			{
				scanResult.Index = 0;
				scanResult.Token = "";
                return scanResult;
			}

            return new PatternScanResult();
		}

		/// <summary>
		/// Finds the given keyword text from the given starting position as a simple find.
		/// </summary>
		/// <param name="text">Text to find.</param>
		/// <param name="startPosition">Starting position.</param>
        /// <param name="maxIndex">Maximum index.</param>
		/// <param name="matchCase">Whether the match case.</param>
		/// <returns>Found PatternScanResult.</returns>
		private PatternScanResult SimpleFindKeyword(string text, int startPosition, int maxIndex, bool matchCase)
		{
			PatternScanResult scanResult;
			while (true)
			{
				scanResult = this.SimpleFind(text, startPosition, maxIndex, matchCase);
				if (scanResult.Token == "")
					return scanResult;

				if (this.CharIsSeparator(text, scanResult.Index - 1) == true &&
					this.CharIsSeparator(text, scanResult.Index + scanResult.Token.Length) == true)
				{
					return scanResult;
				}

				startPosition = scanResult.Index + 1;
				if (startPosition >= text.Length)
				{
					scanResult.Token = "";
					scanResult.Index = 0;
					return scanResult;
				}
			}
		}

		/// <summary>
		/// Finds the given text from the given starting position as a complex find.
		/// </summary>
		/// <param name="text">Text to find.</param>
		/// <param name="startPosition">Starting position.</param>
        /// <param name="maxIndex">Maximum index.</param>
		/// <returns>Found PatternScanResult.</returns>
		private PatternScanResult ComplexFind(string text, int startPosition, int maxIndex)
		{
			PatternScanResult scanResult;
			MatchCollection matchCollection = regex.Matches(text);

			int position = 0;
			string matchValue = "";

            foreach (Match match in matchCollection)
            {
                position = match.Index;
                matchValue = match.Value;
                if (position >= startPosition)
                {
                    if (maxIndex > -1)
                    {
                        if (position <= maxIndex)
                        {
                            scanResult.Index = position;
                            scanResult.Token = matchValue;
                            return scanResult;
                        }
                    }
                    else
                    {
                        scanResult.Index = position;
                        scanResult.Token = matchValue;
                        return scanResult;
                    }
                }
            }

			scanResult.Index = 0;
			scanResult.Token = "";
			return scanResult;
		}

		/// <summary>
		/// Finds the given keyword text from the given starting position as a complex find.
		/// </summary>
		/// <param name="text">Text to find.</param>
		/// <param name="startPosition">Starting position.</param>
        /// <param name="maxIndex">Maximum index.</param>
		/// <returns>Found PatternScanResult.</returns>
		private PatternScanResult ComplexFindKeyword(string text, int startPosition, int maxIndex)
		{
			PatternScanResult scanResult;
			while (true)
			{
				scanResult = this.ComplexFind(text, startPosition, maxIndex);
				if (scanResult.Token == "")
					return scanResult;

				if (this.CharIsSeparator(text, scanResult.Index - 1) == true &&
					this.CharIsSeparator(text, scanResult.Index + scanResult.Token.Length) == true)
				{
					return scanResult;
				}

				startPosition = scanResult.Index + 1;
				if (startPosition >= text.Length)
				{
					scanResult.Token = "";
					scanResult.Index = 0;
					return scanResult;
				}
			}
		}

		#endregion

		#endregion

		/// <summary>
		/// Initializes a new instance of Pattern.
		/// </summary>
		/// <param name="pattern">The Pattern as a string.</param>
		/// <param name="complex">Whether the Pattern is complex.</param>
		public Pattern(string pattern, bool complex)
			: this(pattern, complex, false, false, "")
		{
		}

		/// <summary>
		/// Initializes a new instance of Pattern.
		/// </summary>
		/// <param name="pattern">The Pattern as a string.</param>
		/// <param name="complex">Whether the Pattern is complex.</param>
		/// <param name="separator">Separators the Pattern should use.</param>
		/// <param name="keyword">Whether the Pattern is a keyword.</param>
		public Pattern(string pattern, bool complex, bool separator, bool keyword)
			: this(pattern, complex, separator, keyword, "")
		{
		}

		/// <summary>
		/// Initializes a new instance of Pattern.
		/// </summary>
		/// <param name="pattern">The Pattern as a string.</param>
		/// <param name="complex">Whether the Pattern is complex.</param>
		/// <param name="separator">Separators the Pattern should use.</param>
		/// <param name="keyword">Whether the Pattern is a keyword.</param>
		/// <param name="escapeChar">The escape character of the Pattern.</param>
		public Pattern(string pattern, bool complex, bool separator, bool keyword, string escapeChar)
		{
			this.StringPattern = pattern;
			this.separator = separator;
			this.keyword = keyword;

			if (complex == true)
			{
				this.complex = true;

				regex = new Regex(StringPattern, RegexOptions.Compiled);
				caseRegex = new Regex(StringPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
			}
			else
			{
				this.complex = false;
				patternBuffer = pattern.ToCharArray();
			}

			escapeChar = Regex.Escape(escapeChar);
			string EscPattern = string.Format("(?<=((?<!{0})({0}{0})*))({1})", escapeChar, pattern);
		}
	}
}
